﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Media.Media3D;
using System.Windows.Media;
using System.Windows;
using Eniux.Windows.ThreeDEngine.Geometry;
using Eniux.Windows.ThreeDEngine.ExtensionMethods;
namespace Eniux.Windows.ThreeDEngine.Helper
{
    public class MeshBuilder
    {
        /// <summary>
        ///检查性能限制
        /// </summary>
        /// <remarks>
        /// http://msdn.microsoft.com/en-us/library/bb613553.aspx
        /// Try to keep mesh sizes under these limits:
        /// Positions : 20,001 Point3D instances
        /// TriangleIndices : 60,003 Int32 instances
        /// </remarks>
        public void CheckPerformanceLimits()
        {
            if (this.positions.Count > 20000) Trace.WriteLine(string.Format("Too many positions ({0}).", this.positions.Count));
            if (this.triangleIndices.Count > 60002) Trace.WriteLine(string.Format("Too many triangle indices ({0}).", this.triangleIndices.Count));
        }

        #region 字段、常量

        /// <summary>
        /// MeshGeometry3D 的法向量的集合
        /// </summary>
        internal Vector3DCollection normals;

        /// <summary>
        /// MeshGeometry3D 的顶点位置的集合
        /// </summary>
        internal Point3DCollection positions;

        /// <summary>
        /// MeshGeometry3D 的纹理坐标的集合
        /// </summary>
        internal PointCollection textureCoordinates;

        /// <summary>
        /// MeshGeometry3D 的三角形索引的集合
        /// </summary>
        internal Int32Collection triangleIndices;

        /// <summary>
        /// 
        /// </summary>
        private static readonly Dictionary<int, IList<Point>> CircleDictionary =
            new Dictionary<int, IList<Point>>();

        #endregion

        #region 构造、析构

        /// <summary>
        /// 默认构造
        /// </summary>
        public MeshBuilder()
            : this(true, true)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="generateNormals"></param>
        /// <param name="generateTextureCoordinates"></param>
        public MeshBuilder(bool generateNormals, bool generateTextureCoordinates)
        {
            this.positions = new Point3DCollection();
            this.triangleIndices = new Int32Collection();

            if (generateNormals)
            {
                this.normals = new Vector3DCollection();
            }

            if (generateTextureCoordinates)
            {
                this.textureCoordinates = new PointCollection();
            }
        }

        #endregion

        #region 枚举

        /// <summary>
        /// 盒子面枚举
        /// </summary>
        [Flags]
        public enum BoxFaces
        {
            /// <summary>
            ///   顶
            /// </summary>
            Top = 0x1,

            /// <summary>
            ///  底
            /// </summary>
            Bottom = 0x2,

            /// <summary>
            ///  左
            /// </summary>
            Left = 0x4,

            /// <summary>
            ///  右
            /// </summary>
            Right = 0x8,

            /// <summary>
            ///   前
            /// </summary>
            Front = 0x10,

            /// <summary>
            ///  后
            /// </summary>
            Back = 0x20,

            /// <summary>
            ///  所有面
            /// </summary>
            All = Top | Bottom | Left | Right | Front | Back
        }

        #endregion

        #region 公共属性

        /// <summary>
        /// 获取法线集合
        /// </summary>
        public Vector3DCollection Normals
        {
            get
            {
                return this.normals ?? (this.normals = new Vector3DCollection());
            }
        }

        /// <summary>
        /// 获取所有点集合
        /// </summary>
        public Point3DCollection Positions
        {
            get
            {
                return this.positions ?? (this.positions = new Point3DCollection());
            }
        }

        /// <summary>
        /// 获取纹理坐标
        /// </summary>
        public PointCollection TextureCoordinates
        {
            get
            {
                return this.textureCoordinates ?? (this.textureCoordinates = new PointCollection());
            }
        }

         /// <summary>
         /// 获取三角形
         /// </summary>
        public Int32Collection TriangleIndices
        {
            get
            {
                return this.triangleIndices ?? (this.triangleIndices = new Int32Collection());
            }
        }

        #endregion

        #region 公共方法

        /// <summary>
        /// 获取一个圆截面
        /// </summary>
        /// <param name="thetaDiv"></param>
        /// <returns></returns>
        public static IList<Point> GetCircle(int thetaDiv)
        {
            IList<Point> circle;
            if (!CircleDictionary.TryGetValue(thetaDiv, out circle))
            {
                circle = new PointCollection();
                CircleDictionary.Add(thetaDiv, circle);
                for (int i = 0; i < thetaDiv; i++)
                {
                    double theta = Math.PI * 2 * ((double)i / (thetaDiv - 1));
                    circle.Add(new Point(Math.Cos(theta), -Math.Sin(theta)));
                }
            }

            return circle;
        }

        /// <summary>
        /// 添加一个箭头到网格
        /// </summary>
        /// <param name="point1"></param>
        /// <param name="point2"></param>
        /// <param name="diameter"></param>
        /// <param name="headLength"></param>
        /// <param name="thetaDiv"></param>
        public void AddArrow(Point3D point1, Point3D point2, double diameter, double headLength = 3, int thetaDiv = 18)
        {
            var dir = point2 - point1;
            double length = dir.Length;
            double r = diameter / 2;

            var pc = new PointCollection
                {
                    new Point(0, 0), 
                    new Point(0, r), 
                    new Point(length - diameter * headLength, r), 
                    new Point(length - diameter * headLength, r * 2), 
                    new Point(length, 0)
                };

            this.AddRevolvedGeometry(pc, point1, dir, thetaDiv);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="bb"></param>
        /// <param name="diameter"></param>
        public void AddBoundingBox(Rect3D bb, double diameter)
        {
            var p0 = new Point3D(bb.X, bb.Y, bb.Z);
            var p1 = new Point3D(bb.X, bb.Y + bb.SizeY, bb.Z);
            var p2 = new Point3D(bb.X + bb.SizeX, bb.Y + bb.SizeY, bb.Z);
            var p3 = new Point3D(bb.X + bb.SizeX, bb.Y, bb.Z);
            var p4 = new Point3D(bb.X, bb.Y, bb.Z + bb.SizeZ);
            var p5 = new Point3D(bb.X, bb.Y + bb.SizeY, bb.Z + bb.SizeZ);
            var p6 = new Point3D(bb.X + bb.SizeX, bb.Y + bb.SizeY, bb.Z + bb.SizeZ);
            var p7 = new Point3D(bb.X + bb.SizeX, bb.Y, bb.Z + bb.SizeZ);

            Action<Point3D, Point3D> addEdge = (c1, c2) => this.AddCylinder(c1, c2, diameter, 10);

            addEdge(p0, p1);
            addEdge(p1, p2);
            addEdge(p2, p3);
            addEdge(p3, p0);

            addEdge(p4, p5);
            addEdge(p5, p6);
            addEdge(p6, p7);
            addEdge(p7, p4);

            addEdge(p0, p4);
            addEdge(p1, p5);
            addEdge(p2, p6);
            addEdge(p3, p7);
        }

        /// <summary>
        /// 添加与X、Y和Z轴对齐的框
        /// </summary>
        /// <param name="center">中心点</param>
        /// <param name="xLength">宽度</param>
        /// <param name="yLength">长度</param>
        /// <param name="zLength">高度</param>
        public void AddBox(Point3D center, double xLength, double yLength, double zLength)
        {
            this.AddBox(center, xLength, yLength, zLength, BoxFaces.All);
        }

        /// <summary>
        /// 添加与X、Y和Z轴对齐的立方体
        /// </summary>
        /// <param name="rect"></param>
        public void AddBox(Rect3D rect)
        {
            this.AddBox(
                new Point3D(rect.X + rect.SizeX * 0.5, rect.Y + rect.SizeY * 0.5, rect.Z + rect.SizeZ * 0.5),
                rect.SizeX,
                rect.SizeY,
                rect.SizeZ,
                BoxFaces.All);
        }

        /// <summary>
        /// 在指定的面添加与X、Y和Z轴对齐的立方体
        /// </summary>
        /// <param name="center">中心点</param>
        /// <param name="xLength">宽度</param>
        /// <param name="yLength">长度</param>
        /// <param name="zLength">高度</param>
        /// <param name="faces">面</param>
        public void AddBox(Point3D center, double xLength, double yLength, double zLength, BoxFaces faces)
        {
            if ((faces & BoxFaces.Front) == BoxFaces.Front)
            {
                this.AddCubeFace(center, new Vector3D(0, 1, 0), new Vector3D(0, 0, 1), xLength, yLength, zLength);
            }

            if ((faces & BoxFaces.Left) == BoxFaces.Left)
            {
                this.AddCubeFace(center, new Vector3D(-1, 0, 0), new Vector3D(0, 0, 1), yLength, xLength, zLength);
            }

            if ((faces & BoxFaces.Right) == BoxFaces.Right)
            {
                this.AddCubeFace(center, new Vector3D(1, 0, 0), new Vector3D(0, 0, 1), yLength, xLength, zLength);
            }

            if ((faces & BoxFaces.Back) == BoxFaces.Back)
            {
                this.AddCubeFace(center, new Vector3D(0, -1, 0), new Vector3D(0, 0, 1), xLength, yLength, zLength);
            }

            if ((faces & BoxFaces.Top) == BoxFaces.Top)
            {
                this.AddCubeFace(center, new Vector3D(0, 0, 1), new Vector3D(0, -1, 0), zLength, yLength, xLength);
            }

            if ((faces & BoxFaces.Bottom) == BoxFaces.Bottom)
            {
                this.AddCubeFace(center, new Vector3D(0, 0, -1), new Vector3D(0, 1, 0), zLength, yLength, xLength);
            }
        }

        /// <summary>
        /// 添加一个圆锥
        /// </summary>
        /// <param name="origin"></param>
        /// <param name="direction"></param>
        /// <param name="baseRadius"></param>
        /// <param name="topRadius"></param>
        /// <param name="height"></param>
        /// <param name="baseCap"></param>
        /// <param name="topCap"></param>
        /// <param name="thetaDiv"></param>
        public void AddCone(
            Point3D origin,
            Vector3D direction,
            double baseRadius,
            double topRadius,
            double height,
            bool baseCap,
            bool topCap,
            int thetaDiv)
        {
            var pc = new PointCollection();
            if (baseCap)
            {
                pc.Add(new Point(0, 0));
            }

            pc.Add(new Point(0, baseRadius));
            pc.Add(new Point(height, topRadius));
            if (topCap)
            {
                pc.Add(new Point(height, 0));
            }

            this.AddRevolvedGeometry(pc, origin, direction, thetaDiv);
        }

        /// <summary>
        /// 添加一个盒面
        /// </summary>
        /// <param name="center"></param>
        /// <param name="normal"></param>
        /// <param name="up"></param>
        /// <param name="dist"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public void AddCubeFace(Point3D center, Vector3D normal, Vector3D up, double dist, double width, double height)
        {
            var right = Vector3D.CrossProduct(normal, up);
            var n = normal * dist / 2;
            up *= height / 2;
            right *= width / 2;
            var p1 = center + n - up - right;
            var p2 = center + n - up + right;
            var p3 = center + n + up + right;
            var p4 = center + n + up - right;

            int i0 = this.positions.Count;
            this.positions.Add(p1);
            this.positions.Add(p2);
            this.positions.Add(p3);
            this.positions.Add(p4);
            if (this.normals != null)
            {
                this.normals.Add(normal);
                this.normals.Add(normal);
                this.normals.Add(normal);
                this.normals.Add(normal);
            }

            if (this.textureCoordinates != null)
            {
                this.textureCoordinates.Add(new Point(1, 1));
                this.textureCoordinates.Add(new Point(0, 1));
                this.textureCoordinates.Add(new Point(0, 0));
                this.textureCoordinates.Add(new Point(1, 0));
            }

            this.triangleIndices.Add(i0 + 2);
            this.triangleIndices.Add(i0 + 1);
            this.triangleIndices.Add(i0 + 0);
            this.triangleIndices.Add(i0 + 0);
            this.triangleIndices.Add(i0 + 3);
            this.triangleIndices.Add(i0 + 2);
        }

        /// <summary>
        /// 添加一个柱体到网格
        /// </summary>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <param name="diameter"></param>
        /// <param name="thetaDiv"></param>
        public void AddCylinder(Point3D p1, Point3D p2, double diameter, int thetaDiv)
        {
            Vector3D n = p2 - p1;
            double l = n.Length;
            n.Normalize();
            this.AddCone(p1, n, diameter / 2, diameter / 2, l, false, false, thetaDiv);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pts"></param>
        /// <param name="edges"></param>
        /// <param name="diameter"></param>
        /// <param name="thetaDiv"></param>
        public void AddEdges(IList<Point3D> pts, IList<int> edges, double diameter, int thetaDiv)
        {
            for (int i = 0; i < edges.Count - 1; i += 2)
            {
                this.AddCylinder(pts[edges[i]], pts[edges[i + 1]], diameter, thetaDiv);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="points"></param>
        /// <param name="xDirection"></param>
        /// <param name="p0"></param>
        /// <param name="p1"></param>
        public void AddExtrudedGeometry(IList<Point> points, Vector3D xDirection, Point3D p0, Point3D p1)
        {
            var yDirection = Vector3D.CrossProduct(xDirection, p1 - p0);
            yDirection.Normalize();
            xDirection.Normalize();

            int index0 = this.positions.Count;
            int np = 2 * points.Count;
            foreach (var p in points)
            {
                var v = xDirection * p.X + yDirection * p.Y;
                this.positions.Add(p0 + v);
                this.positions.Add(p1 + v);
                v.Normalize();
                if (this.normals != null)
                {
                    this.normals.Add(v);
                    this.normals.Add(v);
                }

                if (this.textureCoordinates != null)
                {
                    this.textureCoordinates.Add(new Point(0, 0));
                    this.textureCoordinates.Add(new Point(1, 0));
                }

                int i1 = index0 + 1;
                int i2 = (index0 + 2) % np;
                int i3 = (index0 + 2) % np + 1;

                this.triangleIndices.Add(i1);
                this.triangleIndices.Add(i2);
                this.triangleIndices.Add(index0);

                this.triangleIndices.Add(i1);
                this.triangleIndices.Add(i3);
                this.triangleIndices.Add(i2);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="positionsList"></param>
        /// <param name="normalList"></param>
        /// <param name="textureCoordinateList"></param>
        public void AddLoftedGeometry(
            IList<IList<Point3D>> positionsList,
            IList<IList<Vector3D>> normalList,
            IList<IList<Point>> textureCoordinateList)
        {
            int index0 = this.positions.Count;
            int n = -1;
            for (int i = 0; i < positionsList.Count; i++)
            {
                var pc = positionsList[i];

                // check that all curves have same number of points
                if (n == -1)
                {
                    n = pc.Count;
                }

                if (pc.Count != n)
                {
                    throw new InvalidOperationException("All curves should have the same number of points");
                }

                // add the points
                foreach (var p in pc)
                {
                    this.positions.Add(p);
                }

                // add normals 
                if (this.normals != null && normalList != null)
                {
                    var nc = normalList[i];
                    foreach (var normal in nc)
                    {
                        this.normals.Add(normal);
                    }
                }

                // add texcoords
                if (this.textureCoordinates != null && textureCoordinateList != null)
                {
                    var tc = textureCoordinateList[i];
                    foreach (var t in tc)
                    {
                        this.textureCoordinates.Add(t);
                    }
                }
            }

            for (int i = 0; i + 1 < positionsList.Count; i++)
            {
                for (int j = 0; j + 1 < n; j++)
                {
                    int i0 = index0 + i * n + j;
                    int i1 = i0 + n;
                    int i2 = i1 + 1;
                    int i3 = i0 + 1;
                    this.triangleIndices.Add(i0);
                    this.triangleIndices.Add(i1);
                    this.triangleIndices.Add(i2);

                    this.triangleIndices.Add(i2);
                    this.triangleIndices.Add(i3);
                    this.triangleIndices.Add(i0);
                }
            }
        }

        /// <summary>
        /// 添加一个节点
        /// </summary>
        /// <param name="pos"></param>
        /// <param name="normal"></param>
        /// <param name="textureCoordinate"></param>
        public void AddNode(Point3D pos, Vector3D normal, Point textureCoordinate)
        {
            this.Positions.Add(pos);
            if (this.normals != null)
            {
                this.normals.Add(normal);
            }

            if (this.textureCoordinates != null)
            {
                this.textureCoordinates.Add(textureCoordinate);
            }
        }

        /// <summary>
        /// 添加一个空心管
        /// </summary>
        /// <param name="point1"></param>
        /// <param name="point2"></param>
        /// <param name="innerDiameter"></param>
        /// <param name="diameter"></param>
        /// <param name="thetaDiv"></param>
        public void AddPipe(Point3D point1, Point3D point2, double innerDiameter, double diameter, int thetaDiv)
        {
            var dir = point2 - point1;

            double height = dir.Length;
            dir.Normalize();

            var pc = new PointCollection
                {
                    new Point(0, innerDiameter / 2), 
                    new Point(0, diameter / 2), 
                    new Point(height, diameter / 2), 
                    new Point(height, innerDiameter / 2)
                };
            if (innerDiameter > 0)
            {
                pc.Add(new Point(0, innerDiameter / 2));
            }

            this.AddRevolvedGeometry(pc, point1, dir, thetaDiv);
        }

        /// <summary>
        /// 添加一个角锥
        /// http://en.wikipedia.org/wiki/Pyramid_(geometry)
        /// </summary>
        /// <param name="center"></param>
        /// <param name="sideLength"></param>
        /// <param name="height"></param>
        public void AddPyramid(Point3D center, double sideLength, double height)
        {
            var p1 = new Point3D(center.X - sideLength * 0.5, center.Y - sideLength * 0.5, center.Z);
            var p2 = new Point3D(center.X + sideLength * 0.5, center.Y - sideLength * 0.5, center.Z);
            var p3 = new Point3D(center.X + sideLength * 0.5, center.Y + sideLength * 0.5, center.Z);
            var p4 = new Point3D(center.X - sideLength * 0.5, center.Y + sideLength * 0.5, center.Z);
            var p5 = new Point3D(center.X, center.Y, center.Z + height);
            this.AddTriangle(p1, p2, p5);
            this.AddTriangle(p2, p3, p5);
            this.AddTriangle(p3, p4, p5);
            this.AddTriangle(p4, p1, p5);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="p0"></param>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <param name="p3"></param>
        public void AddQuad(Point3D p0, Point3D p1, Point3D p2, Point3D p3)
        {
            var uv0 = new Point(0, 0);
            var uv1 = new Point(1, 0);
            var uv2 = new Point(0, 1);
            var uv3 = new Point(1, 1);
            this.AddQuad(p0, p1, p2, p3, uv0, uv1, uv2, uv3);
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="p0"></param>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <param name="p3"></param>
        /// <param name="uv0"></param>
        /// <param name="uv1"></param>
        /// <param name="uv2"></param>
        /// <param name="uv3"></param>
        public void AddQuad(Point3D p0, Point3D p1, Point3D p2, Point3D p3, Point uv0, Point uv1, Point uv2, Point uv3)
        {
            int i0 = this.positions.Count;

            this.positions.Add(p0);
            this.positions.Add(p1);
            this.positions.Add(p2);
            this.positions.Add(p3);

            if (this.textureCoordinates != null)
            {
                this.textureCoordinates.Add(uv0);
                this.textureCoordinates.Add(uv1);
                this.textureCoordinates.Add(uv2);
                this.textureCoordinates.Add(uv3);
            }

            if (this.normals != null)
            {
                var w = Vector3D.CrossProduct(p3 - p0, p1 - p0);
                w.Normalize();
                this.normals.Add(w);
                this.normals.Add(w);
                this.normals.Add(w);
                this.normals.Add(w);
            }

            this.triangleIndices.Add(i0 + 0);
            this.triangleIndices.Add(i0 + 1);
            this.triangleIndices.Add(i0 + 2);

            this.triangleIndices.Add(i0 + 2);
            this.triangleIndices.Add(i0 + 3);
            this.triangleIndices.Add(i0 + 0);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="points"></param>
        /// <param name="norms"></param>
        /// <param name="texCoords"></param>
        public void AddQuads(IList<Point3D> points, IList<Vector3D> norms, IList<Point> texCoords)
        {
            Debug.Assert(points.Count > 0 && points.Count % 4 == 0);
            int index0 = this.positions.Count;
            foreach (var p in points)
            {
                this.positions.Add(p);
            }

            if (this.textureCoordinates != null && texCoords != null)
            {
                foreach (var tc in texCoords)
                {
                    this.textureCoordinates.Add(tc);
                }
            }

            if (this.normals != null && norms != null)
            {
                foreach (var n in norms)
                {
                    this.normals.Add(n);
                }
            }

            int indexEnd = this.positions.Count;
            for (int i = index0; i + 3 < indexEnd; i++)
            {
                this.triangleIndices.Add(i);
                this.triangleIndices.Add(i + 1);
                this.triangleIndices.Add(i + 2);

                this.triangleIndices.Add(i + 2);
                this.triangleIndices.Add(i + 3);
                this.triangleIndices.Add(i);
            }
        }

        /// <summary>
        /// 添加一个矩形
        /// </summary>
        /// <param name="points"></param>
        /// <param name="columns"></param>
        public void AddRectangularMesh(IList<Point3D> points, int columns)
        {
            int index0 = this.Positions.Count;

            foreach (var pt in points)
            {
                this.positions.Add(pt);
            }

            int rows = points.Count / columns;

            this.AddRectangularMeshTriangleIndices(index0, rows, columns);
            if (this.normals != null)
            {
                this.AddRectangularMeshNormals(index0, rows, columns);
            }

            if (this.textureCoordinates != null)
            {
                this.AddRectangularMeshTextureCoordinates(rows, columns);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pts"></param>
        /// <param name="texCoords"></param>
        /// <param name="closed0"></param>
        /// <param name="closed1"></param>
        public void AddRectangularMesh(
            Point3D[,] pts, Point[,] texCoords = null, bool closed0 = false, bool closed1 = false)
        {
            int rows = pts.GetUpperBound(0) + 1;
            int columns = pts.GetUpperBound(1) + 1;
            int index0 = this.positions.Count;
            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < columns; j++)
                {
                    this.positions.Add(pts[i, j]);
                }
            }

            this.AddRectangularMeshTriangleIndices(index0, rows, columns, closed0, closed1);

            if (this.normals != null)
            {
                this.AddRectangularMeshNormals(index0, rows, columns);
            }

            if (this.textureCoordinates != null)
            {
                if (texCoords != null)
                {
                    for (int i = 0; i < rows; i++)
                    {
                        for (int j = 0; j < columns; j++)
                        {
                            this.textureCoordinates.Add(texCoords[i, j]);
                        }
                    }
                }
                else
                {
                    this.AddRectangularMeshTextureCoordinates(rows, columns);
                }
            }
        }

        /// <summary>
        /// 添加一个立方体
        /// </summary>
        /// <param name="center"></param>
        /// <param name="radius"></param>
        /// <param name="shareVertices"></param>
        public void AddRegularIcosahedron(Point3D center, double radius, bool shareVertices)
        {
            double a = Math.Sqrt(2.0 / (5.0 + Math.Sqrt(5.0)));

            double b = Math.Sqrt(2.0 / (5.0 - Math.Sqrt(5.0)));

            var icosahedronIndices = new[]
            {
                1, 4, 0, 4, 9, 0, 4, 5, 9, 8, 5, 4, 1, 8, 4, 1, 10, 8, 10, 3, 8, 8, 3, 5, 3, 2, 5, 3, 7, 2, 3, 10, 7, 10, 
                6, 7, 6, 11, 7, 6, 0, 11, 6, 1, 0, 10, 1, 6, 11, 0, 9, 2, 11, 9, 5, 2, 9, 11, 2, 7
            };

            var icosahedronVertices = new[]
            {
                new Vector3D(-a, 0, b), new Vector3D(a, 0, b), new Vector3D(-a, 0, -b), new Vector3D(a, 0, -b), 
                new Vector3D(0, b, a), new Vector3D(0, b, -a), new Vector3D(0, -b, a), new Vector3D(0, -b, -a), 
                new Vector3D(b, a, 0), new Vector3D(-b, a, 0), new Vector3D(b, -a, 0), new Vector3D(-b, -a, 0)
            };


            // http://en.wikipedia.org/wiki/Icosahedron
            // http://www.gamedev.net/community/forums/topic.asp?topic_id=283350
            if (shareVertices)
            {
                int index0 = this.positions.Count;
                foreach (var v in icosahedronVertices)
                {
                    this.positions.Add(center + v * radius);
                }

                foreach (int i in icosahedronIndices)
                {
                    this.triangleIndices.Add(index0 + i);
                }
            }
            else
            {
                for (int i = 0; i + 2 < icosahedronIndices.Length; i += 3)
                {
                    this.AddTriangle(
                        center + icosahedronVertices[icosahedronIndices[i]] * radius,
                        center + icosahedronVertices[icosahedronIndices[i + 1]] * radius,
                        center + icosahedronVertices[icosahedronIndices[i + 2]] * radius);
                }
            }
        }

        /// <summary>
        /// 添加旋转曲面
        ///  http://en.wikipedia.org/wiki/Surface_of_revolution
        /// </summary>
        /// <param name="points"></param>
        /// <param name="origin"></param>
        /// <param name="direction"></param>
        /// <param name="thetaDiv"></param>
        public void AddRevolvedGeometry(IList<Point> points, Point3D origin, Vector3D direction, int thetaDiv)
        {
            direction.Normalize();

            // Find two unit vectors orthogonal to the specified direction
            var u = direction.FindAnyPerpendicular();
            var v = Vector3D.CrossProduct(direction, u);

            u.Normalize();
            v.Normalize();

            var circle = GetCircle(thetaDiv);

            int index0 = this.positions.Count;
            int nPoints = points.Count;

            int totalNodes = (points.Count - 1) * 2 * thetaDiv;
            int rowNodes = (points.Count - 1) * 2;

            for (int i = 0; i < thetaDiv; i++)
            {
                Vector3D w = v * circle[i].X + u * circle[i].Y;

                for (int j = 0; j + 1 < nPoints; j++)
                {
                    // Add segment
                    var q1 = origin + direction * points[j].X + w * points[j].Y;
                    var q2 = origin + direction * points[j + 1].X + w * points[j + 1].Y;

                    // todo:should not add segment if q1==q2 (corner point)
                    // const double eps = 1e-6;
                    // if (Point3D.Subtract(q1, q2).LengthSquared < eps)
                    // continue;
                    double tx = points[j + 1].X - points[j].X;
                    double ty = points[j + 1].Y - points[j].Y;

                    var normal = -direction * ty + w * tx;
                    normal.Normalize();

                    this.positions.Add(q1);
                    this.positions.Add(q2);

                    if (this.normals != null)
                    {
                        this.normals.Add(normal);
                        this.normals.Add(normal);
                    }

                    if (this.textureCoordinates != null)
                    {
                        this.textureCoordinates.Add(new Point((double)i / (thetaDiv - 1), (double)j / (nPoints - 1)));
                        this.textureCoordinates.Add(
                            new Point((double)i / (thetaDiv - 1), (double)(j + 1) / (nPoints - 1)));
                    }

                    int i0 = index0 + i * rowNodes + j * 2;
                    int i1 = i0 + 1;
                    int i2 = index0 + ((i + 1) * rowNodes + j * 2) % totalNodes;
                    int i3 = i2 + 1;

                    this.triangleIndices.Add(i1);
                    this.triangleIndices.Add(i0);
                    this.triangleIndices.Add(i2);

                    this.triangleIndices.Add(i1);
                    this.triangleIndices.Add(i2);
                    this.triangleIndices.Add(i3);
                }
            }
        }

        /// <summary>
        /// 添加一个球体
        /// </summary>
        /// <param name="center"></param>
        /// <param name="radius"></param>
        /// <param name="subdivisions"></param>
        public void AddSphere(Point3D center, double radius, int subdivisions)
        {
            int p0 = positions.Count;
            Append(GetUnitSphere(subdivisions));
            int p1 = positions.Count;
            for (int i = p0; i < p1; i++)
                positions[i] = center + radius * positions[i].ToVector3D();
        }

        static Dictionary<int, MeshGeometry3D> unitSphereCache = new Dictionary<int, MeshGeometry3D>();

        private static MeshGeometry3D GetUnitSphere(int subdivisions)
        {
            if (unitSphereCache.ContainsKey(subdivisions))
            {
                return unitSphereCache[subdivisions];
            }

            var mb = new MeshBuilder(false, false);
            mb.AddRegularIcosahedron(new Point3D(), 1, false);
            for (int i = 0; i < subdivisions; i++)
                mb.SubdivideLinear();
            for (int i = 0; i < mb.positions.Count; i++)
            {
                var v = mb.Positions[i].ToVector3D();
                v.Normalize();
                mb.Positions[i] = v.ToPoint3D();
            }
            var mesh = mb.ToMesh();
            unitSphereCache[subdivisions] = mesh;
            return mesh;
        }


        /// <summary>
        /// 添加一个球体
        /// </summary>
        /// <param name="center"></param>
        /// <param name="radius"></param>
        /// <param name="thetaDiv"></param>
        /// <param name="phiDiv"></param>
        public void AddSphere(Point3D center, double radius, int thetaDiv = 20, int phiDiv = 10)
        {
            int index0 = this.Positions.Count;
            double dt = 2 * Math.PI / thetaDiv;
            double dp = Math.PI / phiDiv;

            // todo: there are better ways to tesselate the sphere

            for (int pi = 0; pi <= phiDiv; pi++)
            {
                double phi = pi * dp;

                for (int ti = 0; ti <= thetaDiv; ti++)
                {
                    // we want to start the mesh on the x axis
                    double theta = ti * dt;

                    // Spherical coordinates
                    // http://mathworld.wolfram.com/SphericalCoordinates.html
                    double x = Math.Cos(theta) * Math.Sin(phi);
                    double y = Math.Sin(theta) * Math.Sin(phi);
                    double z = Math.Cos(phi);

                    var p = new Point3D(center.X + radius * x, center.Y + radius * y, center.Z + radius * z);
                    this.positions.Add(p);

                    if (this.normals != null)
                    {
                        var n = new Vector3D(x, y, z);
                        this.normals.Add(n);
                    }

                    if (this.textureCoordinates != null)
                    {
                        var uv = new Point(theta / (2 * Math.PI), phi / Math.PI);
                        this.textureCoordinates.Add(uv);
                    }
                }
            }

            this.AddRectangularMeshTriangleIndices(index0, phiDiv + 1, thetaDiv + 1, true);
        }

        /// <summary>
        /// 添加一个三角形
        /// </summary>
        /// <param name="p0"></param>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        public void AddTriangle(Point3D p0, Point3D p1, Point3D p2)
        {
            var uv0 = new Point(0, 0);
            var uv1 = new Point(1, 0);
            var uv2 = new Point(0, 1);
            this.AddTriangle(p0, p1, p2, uv0, uv1, uv2);
        }

        /// <summary>
        /// 添加一个三角形
        /// </summary>
        /// <param name="p0"></param>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <param name="uv0"></param>
        /// <param name="uv1"></param>
        /// <param name="uv2"></param>
        public void AddTriangle(Point3D p0, Point3D p1, Point3D p2, Point uv0, Point uv1, Point uv2)
        {
            int i0 = this.positions.Count;

            this.positions.Add(p0);
            this.positions.Add(p1);
            this.positions.Add(p2);

            if (this.textureCoordinates != null)
            {
                this.textureCoordinates.Add(uv0);
                this.textureCoordinates.Add(uv1);
                this.textureCoordinates.Add(uv2);
            }

            if (this.normals != null)
            {
                var w = Vector3D.CrossProduct(p1 - p0, p2 - p0);
                w.Normalize();
                this.normals.Add(w);
                this.normals.Add(w);
                this.normals.Add(w);
            }

            this.triangleIndices.Add(i0 + 0);
            this.triangleIndices.Add(i0 + 1);
            this.triangleIndices.Add(i0 + 2);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="points"></param>
        public void AddTriangleFan(IList<Point3D> points)
        {
            this.AddTriangleFan(points, null, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="vertices"></param>
        public void AddTriangleFan(IList<int> vertices)
        {
            for (int i = 0; i + 2 < vertices.Count; i++)
            {
                this.triangleIndices.Add(vertices[0]);
                this.triangleIndices.Add(vertices[i + 1]);
                this.triangleIndices.Add(vertices[i + 2]);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="points"></param>
        /// <param name="norms"></param>
        /// <param name="texCoords"></param>
        public void AddTriangleFan(IList<Point3D> points, IList<Vector3D> norms, IList<Point> texCoords)
        {
            int index0 = this.positions.Count;
            foreach (var p in points)
            {
                this.positions.Add(p);
            }

            if (this.textureCoordinates != null && texCoords != null)
            {
                foreach (var tc in texCoords)
                {
                    this.textureCoordinates.Add(tc);
                }
            }

            if (this.normals != null && norms != null)
            {
                foreach (var n in norms)
                {
                    this.normals.Add(n);
                }
            }

            int indexEnd = this.positions.Count;
            for (int i = index0; i + 2 < indexEnd; i++)
            {
                this.triangleIndices.Add(index0);
                this.triangleIndices.Add(i + 1);
                this.triangleIndices.Add(i + 2);
            }
        }

        /// <summary>
        /// 在网格中添加三角形
        ///  http://en.wikipedia.org/wiki/Triangle_strip
        /// </summary>
        /// <param name="pos"></param>
        /// <param name="norms"></param>
        /// <param name="texCoords"></param>
        public void AddTriangleStrip(IList<Point3D> pos, IList<Vector3D> norms, IList<Point> texCoords)
        {
            int index0 = this.positions.Count;
            for (int i = 0; i < pos.Count; i++)
            {
                this.positions.Add(pos[i]);
                if (this.normals != null && norms != null)
                {
                    this.normals.Add(norms[i]);
                }

                if (this.textureCoordinates != null && texCoords != null)
                {
                    this.textureCoordinates.Add(texCoords[i]);
                }
            }

            int indexEnd = this.positions.Count;
            for (int i = index0; i + 2 < indexEnd; i += 2)
            {
                this.triangleIndices.Add(i);
                this.triangleIndices.Add(i + 1);
                this.triangleIndices.Add(i + 2);

                if (i + 3 < indexEnd)
                {
                    this.triangleIndices.Add(i + 1);
                    this.triangleIndices.Add(i + 3);
                    this.triangleIndices.Add(i + 2);
                }
            }
        }

        /// <summary>
        /// 添加三角形
        /// </summary>
        /// <param name="points"></param>
        /// <param name="norms"></param>
        /// <param name="texCoords"></param>
        public void AddTriangles(IList<Point3D> points, IList<Vector3D> norms, IList<Point> texCoords)
        {
            Debug.Assert(points.Count > 0 && points.Count % 3 == 0);
            int index0 = this.positions.Count;
            foreach (var p in points)
            {
                this.positions.Add(p);
            }

            if (this.textureCoordinates != null && texCoords != null)
            {
                foreach (var tc in texCoords)
                {
                    this.textureCoordinates.Add(tc);
                }
            }

            if (this.normals != null && norms != null)
            {
                foreach (var n in norms)
                {
                    this.normals.Add(n);
                }
            }

            int indexEnd = this.positions.Count;
            for (int i = index0; i + 2 < indexEnd; i++)
            {
                this.triangleIndices.Add(i);
                this.triangleIndices.Add(i + 1);
                this.triangleIndices.Add(i + 2);
            }
        }

        /// <summary>
        /// 添加一个管体
        /// </summary>
        /// <param name="path"></param>
        /// <param name="values"></param>
        /// <param name="diameter"></param>
        /// <param name="thetaDiv"></param>
        /// <param name="isTubeClosed"></param>
        public void AddTube(IList<Point3D> path, double[] values, double[] diameter, int thetaDiv, bool isTubeClosed)
        {
            var pc = GetCircle(thetaDiv);
            this.AddTube(path, values, diameter, pc, isTubeClosed, true);
        }

        /// <summary>
        /// 添加一个管体
        /// </summary>
        /// <param name="path"></param>
        /// <param name="diameter"></param>
        /// <param name="thetaDiv"></param>
        /// <param name="isTubeClosed"></param>
        public void AddTube(IList<Point3D> path, double diameter, int thetaDiv, bool isTubeClosed)
        {
            this.AddTube(path, null, new[] { diameter }, thetaDiv, isTubeClosed);
        }


        /// <summary>
        /// 添加一个管体
        /// </summary>
        /// <param name="path"></param>
        /// <param name="values"></param>
        /// <param name="diameter"></param>
        /// <param name="section"></param>
        /// <param name="isTubeClosed"></param>
        /// <param name="isSectionClosed"></param>
        public void AddTube(
            IList<Point3D> path,
            double[] values,
            double[] diameter,
            IList<Point> section,
            bool isTubeClosed,
            bool isSectionClosed)
        {
            int index0 = this.positions.Count;
            int pathLength = path.Count;
            int sectionLength = section.Count;
            if (pathLength < 2 || sectionLength < 2)
            {
                return;
            }

            var up = (path[1] - path[0]).FindAnyPerpendicular();

            int diameterCount = diameter != null ? diameter.Length : 0;
            int valuesCount = values != null ? values.Length : 0;

            for (int i = 0; i < pathLength; i++)
            {
                double r = diameter != null ? diameter[i % diameterCount] / 2 : 1;
                int i0 = i > 0 ? i - 1 : i;
                int i1 = i + 1 < pathLength ? i + 1 : i;

                var forward = path[i1] - path[i0];
                var right = Vector3D.CrossProduct(up, forward);
                up = Vector3D.CrossProduct(forward, right);
                up.Normalize();
                right.Normalize();
                var u = right;
                var v = up;
                for (int j = 0; j < sectionLength; j++)
                {
                    var w = section[j].X * u * r + section[j].Y * v * r;
                    var q = path[i] + w;
                    this.positions.Add(q);
                    if (this.normals != null)
                    {
                        w.Normalize();
                        this.normals.Add(w);
                    }

                    if (this.textureCoordinates != null)
                    {
                        this.textureCoordinates.Add(
                            values != null ? new Point(values[i % valuesCount], 0) : new Point());
                    }
                }
            }

            this.AddRectangularMeshTriangleIndices(index0, pathLength, sectionLength, isSectionClosed, isTubeClosed);
        }

        /// <summary>
        /// 追加到指定的网格
        /// </summary>
        /// <param name="mesh"></param>
        public void Append(MeshBuilder mesh)
        {
            int i0 = this.Positions.Count;
            foreach (var p in mesh.Positions)
            {
                this.Positions.Add(p);
            }

            foreach (int idx in mesh.TriangleIndices)
            {
                this.TriangleIndices.Add(idx + i0);
            }

            if (this.normals != null)
            {
                foreach (var p in mesh.normals)
                {
                    this.normals.Add(p);
                }
            }

            if (this.textureCoordinates != null)
            {
                foreach (var p in mesh.textureCoordinates)
                {
                    this.textureCoordinates.Add(p);
                }
            }
        }

        /// <summary>
        /// 追加到指定的网格
        /// </summary>
        /// <param name="mesh"></param>
        public void Append(MeshGeometry3D mesh)
        {
            int i0 = this.Positions.Count;
            foreach (var p in mesh.Positions)
            {
                this.Positions.Add(p);
            }

            foreach (int idx in mesh.TriangleIndices)
            {
                this.TriangleIndices.Add(idx + i0);
            }

            if (this.normals != null && mesh.Normals != null)
            {
                foreach (var p in mesh.Normals)
                {
                    this.normals.Add(p);
                }
            }

            if (this.textureCoordinates != null && mesh.TextureCoordinates != null)
            {
                foreach (var p in mesh.TextureCoordinates)
                {
                    this.textureCoordinates.Add(p);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pts"></param>
        /// <param name="tri"></param>
        public void Append(IList<Point3D> pts, IList<int> tri)
        {
            int index0 = this.positions.Count;
            foreach (var p in pts)
            {
                this.positions.Add(p);
            }

            foreach (int i in tri)
            {
                this.triangleIndices.Add(index0 + i);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="p"></param>
        /// <param name="d"></param>
        /// <param name="eps"></param>
        /// <param name="chamferPoints"></param>
        public void ChamferCorner(Point3D p, double d, double eps = 1e-6, ICollection<Point3D> chamferPoints = null)
        {
            this.NoSharedVertices();

            this.normals = null;
            this.textureCoordinates = null;

            var cornerNormal = this.FindCornerNormal(p, eps);

            var newCornerPoint = p - cornerNormal * d;
            int index0 = this.positions.Count;
            this.positions.Add(newCornerPoint);

            var plane = new Plane3D(newCornerPoint, cornerNormal);

            int ntri = this.triangleIndices.Count;

            for (int i = 0; i < ntri; i += 3)
            {
                int i0 = i;
                int i1 = i + 1;
                int i2 = i + 2;
                var p0 = this.positions[this.triangleIndices[i0]];
                var p1 = this.positions[this.triangleIndices[i1]];
                var p2 = this.positions[this.triangleIndices[i2]];
                double d0 = (p - p0).LengthSquared;
                double d1 = (p - p1).LengthSquared;
                double d2 = (p - p2).LengthSquared;
                double mind = Math.Min(d0, Math.Min(d1, d2));
                if (mind > eps)
                {
                    continue;
                }

                if (d1 < eps)
                {
                    i0 = i + 1;
                    i1 = i + 2;
                    i2 = i;
                }

                if (d2 < eps)
                {
                    i0 = i + 2;
                    i1 = i;
                    i2 = i + 1;
                }

                p0 = this.positions[this.triangleIndices[i0]];
                p1 = this.positions[this.triangleIndices[i1]];
                p2 = this.positions[this.triangleIndices[i2]];

                // p0 is the corner vertex (at index i0)
                // find the intersections between the chamfer plane and the two edges connected to the corner
                var p01 = plane.LineIntersection(p0, p1);
                var p02 = plane.LineIntersection(p0, p2);

                if (p01 == null)
                {
                    continue;
                }

                if (p02 == null)
                {
                    continue;
                }

                if (chamferPoints != null)
                {
                    // add the chamfered points
                    if (!chamferPoints.Contains(p01.Value))
                    {
                        chamferPoints.Add(p01.Value);
                    }

                    if (!chamferPoints.Contains(p02.Value))
                    {
                        chamferPoints.Add(p02.Value);
                    }
                }

                int i01 = i0;

                // change the original triangle to use the first chamfer point
                this.positions[this.triangleIndices[i01]] = p01.Value;

                int i02 = this.positions.Count;
                this.positions.Add(p02.Value);

                // add a new triangle for the other chamfer point
                this.triangleIndices.Add(i01);
                this.triangleIndices.Add(i2);
                this.triangleIndices.Add(i02);

                // add a triangle connecting the chamfer points and the new corner point
                this.triangleIndices.Add(index0);
                this.triangleIndices.Add(i01);
                this.triangleIndices.Add(i02);
            }

            this.NoSharedVertices();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="freeze"></param>
        /// <returns></returns>
        public MeshGeometry3D ToMesh(bool freeze = false)
        {
            var mg = new MeshGeometry3D
            {
                Positions = this.positions,
                TriangleIndices = this.triangleIndices,
                Normals = this.normals,
                TextureCoordinates = this.textureCoordinates
            };
            if (freeze)
            {
                mg.Freeze();
            }

            return mg;
        }

        #endregion

        #region 私有方法

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index0"></param>
        /// <param name="rows"></param>
        /// <param name="columns"></param>
        private void AddRectangularMeshNormals(int index0, int rows, int columns)
        {
            for (int i = 0; i < rows; i++)
            {
                int i1 = i + 1;
                if (i1 == rows)
                {
                    i1--;
                }

                int i0 = i1 - 1;
                for (int j = 0; j < columns; j++)
                {
                    int j1 = j + 1;
                    if (j1 == columns)
                    {
                        j1--;
                    }

                    int j0 = j1 - 1;
                    var normal =
                        Vector3D.CrossProduct(
                            Point3D.Subtract(
                                this.positions[index0 + i1 * columns + j0], this.positions[index0 + i0 * columns + j0]),
                            Point3D.Subtract(
                                this.positions[index0 + i0 * columns + j1], this.positions[index0 + i0 * columns + j0]));
                    normal.Normalize();
                    this.normals.Add(normal);
                }
            }
        }


       /// <summary>
       /// 
       /// </summary>
       /// <param name="rows"></param>
       /// <param name="columns"></param>
        private void AddRectangularMeshTextureCoordinates(int rows, int columns)
        {
            for (int i = 0; i < rows; i++)
            {
                double v = (double)i / (rows - 1);
                for (int j = 0; j < columns; j++)
                {
                    double u = (double)j / (columns - 1);
                    this.textureCoordinates.Add(new Point(u, v));
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index0"></param>
        /// <param name="rows"></param>
        /// <param name="columns"></param>
        /// <param name="isSpherical"></param>
        private void AddRectangularMeshTriangleIndices(int index0, int rows, int columns, bool isSpherical = false)
        {
            for (int i = 0; i < rows - 1; i++)
            {
                for (int j = 0; j < columns - 1; j++)
                {
                    int ij = i * columns + j;
                    if (!isSpherical || i > 0)
                    {
                        this.triangleIndices.Add(index0 + ij);
                        this.triangleIndices.Add(index0 + ij + 1 + columns);
                        this.triangleIndices.Add(index0 + ij + 1);
                    }

                    if (!isSpherical || i < rows - 2)
                    {
                        this.triangleIndices.Add(index0 + ij + 1 + columns);
                        this.triangleIndices.Add(index0 + ij);
                        this.triangleIndices.Add(index0 + ij + columns);
                    }
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index0"></param>
        /// <param name="rows"></param>
        /// <param name="columns"></param>
        /// <param name="rowsClosed"></param>
        /// <param name="columnsClosed"></param>
        private void AddRectangularMeshTriangleIndices(
            int index0, int rows, int columns, bool rowsClosed, bool columnsClosed)
        {
            int m2 = rows - 1;
            int n2 = columns - 1;
            if (columnsClosed)
            {
                m2++;
            }

            if (rowsClosed)
            {
                n2++;
            }

            for (int i = 0; i < m2; i++)
            {
                for (int j = 0; j < n2; j++)
                {
                    int ij00 = index0 + i * columns + j;
                    int ij01 = index0 + i * columns + (j + 1) % columns;
                    int ij10 = index0 + ((i + 1) % rows) * columns + j;
                    int ij11 = index0 + ((i + 1) % rows) * columns + (j + 1) % columns;
                    this.triangleIndices.Add(ij00);
                    this.triangleIndices.Add(ij11);
                    this.triangleIndices.Add(ij01);

                    this.triangleIndices.Add(ij11);
                    this.triangleIndices.Add(ij00);
                    this.triangleIndices.Add(ij10);
                }
            }
        }

        /// <summary>
        /// 查找法线
        /// </summary>
        /// <param name="p"></param>
        /// <param name="eps"></param>
        /// <returns></returns>
        private Vector3D FindCornerNormal(Point3D p, double eps)
        {
            var sum = new Vector3D();
            int count = 0;
            var addedNormals = new HashSet<Vector3D>();
            for (int i = 0; i < this.triangleIndices.Count; i += 3)
            {
                int i0 = i;
                int i1 = i + 1;
                int i2 = i + 2;
                var p0 = this.positions[this.triangleIndices[i0]];
                var p1 = this.positions[this.triangleIndices[i1]];
                var p2 = this.positions[this.triangleIndices[i2]];

                // check if any of the vertices are on the corner
                double d0 = (p - p0).LengthSquared;
                double d1 = (p - p1).LengthSquared;
                double d2 = (p - p2).LengthSquared;
                double mind = Math.Min(d0, Math.Min(d1, d2));
                if (mind > eps)
                {
                    continue;
                }

                // calculate the triangle normal and check if this face is already added
                var normal = Vector3D.CrossProduct(p1 - p0, p2 - p0);
                normal.Normalize();

                // todo: need to use the epsilon value to compare the normals?
                if (addedNormals.Contains(normal))
                {
                    continue;
                }

                // todo: this does not work yet
                // double dp = 1;
                // foreach (var n in addedNormals)
                // {
                // dp = Math.Abs(Vector3D.DotProduct(n, normal) - 1);
                // if (dp < eps)
                // continue;
                // }
                // if (dp < eps)
                // {
                // continue;
                // }
                count++;
                sum += normal;
                addedNormals.Add(normal);
            }

            if (count == 0)
            {
                return new Vector3D();
            }

            return sum * (1.0 / count);
        }

        /// <summary>
        /// 确保三角形没有共享的点
        /// </summary>
        private void NoSharedVertices()
        {
            var p = new Point3DCollection();
            var ti = new Int32Collection();
            Vector3DCollection n = null;
            if (this.normals != null)
            {
                n = new Vector3DCollection();
            }

            PointCollection tc = null;
            if (this.textureCoordinates != null)
            {
                tc = new PointCollection();
            }

            for (int i = 0; i < this.triangleIndices.Count; i += 3)
            {
                int i0 = i;
                int i1 = i + 1;
                int i2 = i + 2;
                int index0 = this.triangleIndices[i0];
                int index1 = this.triangleIndices[i1];
                int index2 = this.triangleIndices[i2];
                var p0 = this.positions[index0];
                var p1 = this.positions[index1];
                var p2 = this.positions[index2];
                p.Add(p0);
                p.Add(p1);
                p.Add(p2);
                ti.Add(i0);
                ti.Add(i1);
                ti.Add(i2);
                if (n != null)
                {
                    n.Add(this.normals[index0]);
                    n.Add(this.normals[index1]);
                    n.Add(this.normals[index2]);
                }

                if (tc != null)
                {
                    tc.Add(this.textureCoordinates[index0]);
                    tc.Add(this.textureCoordinates[index1]);
                    tc.Add(this.textureCoordinates[index2]);
                }
            }

            this.positions = p;
            this.triangleIndices = ti;
            this.normals = n;
            this.textureCoordinates = tc;
        }

        #endregion

        /// <summary>
        /// 缩放点和法线
        /// </summary>
        /// <param name="scaleX">x轴向缩放因子</param>
        /// <param name="scaleY">y轴向缩放因子</param>
        /// <param name="scaleZ">z轴向缩放因子</param>
        public void Scale(double scaleX, double scaleY, double scaleZ)
        {
            for (int i = 0; i < Positions.Count; i++)
            {
                Positions[i] = new Point3D(Positions[i].X * scaleX, Positions[i].Y * scaleY, Positions[i].Z * scaleZ);
            }
            if (Normals != null)
            {
                for (int i = 0; i < Normals.Count; i++)
                {
                    Normals[i] = new Vector3D(Normals[i].X * scaleX, Normals[i].Y * scaleY, Normals[i].Z * scaleZ);
                    Normals[i].Normalize();
                }
            }
        }

        private Point3D Sum(params Point3D[] points)
        {
            double x = 0;
            double y = 0;
            double z = 0;
            foreach (var p in points)
            {
                x += p.X;
                y += p.Y;
                z += p.Z;
            }
            return new Point3D(x, y, z);
        }

        private Point3D Multiply(Point3D p, double d)
        {
            return new Point3D(p.X * d, p.Y * d, p.Z * d);
        }

        /// <summary>
        /// 线性细分网格
        /// </summary>
        /// <param name="barycentric"></param>
        public void SubdivideLinear(bool barycentric = false)
        {
            if (barycentric)
                SubdivideBarycentric();
            else
                Subdivide4();
        }

        /// <summary>
        /// 
        /// </summary>
        private void Subdivide4()
        {
            // Each triangle is divided into four subtriangles, adding new vertices in the middle of each edge.
            int ip = Positions.Count;
            int ntri = TriangleIndices.Count;
            for (int i = 0; i < ntri; i += 3)
            {
                int i0 = TriangleIndices[i];
                int i1 = TriangleIndices[i + 1];
                int i2 = TriangleIndices[i + 2];
                var p0 = Positions[i0];
                var p1 = Positions[i1];
                var p2 = Positions[i2];
                var v01 = p1 - p0;
                var v12 = p2 - p1;
                var v20 = p0 - p2;
                var p01 = p0 + v01 * 0.5;
                var p12 = p1 + v12 * 0.5;
                var p20 = p2 + v20 * 0.5;

                int i01 = ip++;
                int i12 = ip++;
                int i20 = ip++;

                Positions.Add(p01);
                Positions.Add(p12);
                Positions.Add(p20);

                if (normals != null)
                {
                    var n = Normals[i0];
                    Normals.Add(n);
                    Normals.Add(n);
                    Normals.Add(n);
                }
                if (textureCoordinates != null)
                {
                    var uv0 = TextureCoordinates[i0];
                    var uv1 = TextureCoordinates[i0 + 1];
                    var uv2 = TextureCoordinates[i0 + 2];
                    var t01 = uv1 - uv0;
                    var t12 = uv2 - uv1;
                    var t20 = uv0 - uv2;
                    var uv01 = uv0 + t01 * 0.5;
                    var uv12 = uv1 + t12 * 0.5;
                    var uv20 = uv2 + t20 * 0.5;
                    TextureCoordinates.Add(uv01);
                    TextureCoordinates.Add(uv12);
                    TextureCoordinates.Add(uv20);
                }

                // TriangleIndices[i ] = i0;
                TriangleIndices[i + 1] = i01;
                TriangleIndices[i + 2] = i20;

                TriangleIndices.Add(i01);
                TriangleIndices.Add(i1);
                TriangleIndices.Add(i12);

                TriangleIndices.Add(i12);
                TriangleIndices.Add(i2);
                TriangleIndices.Add(i20);

                TriangleIndices.Add(i01);
                TriangleIndices.Add(i12);
                TriangleIndices.Add(i20);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void SubdivideBarycentric()
        {
            // The BCS of a triangle S divides it into six triangles; each part has one vertex v2 at the 
            // barycenter of S, another one v1 at the midpoint of some side, and the last one v0 at one 
            // of the original vertices.
            int im = Positions.Count;
            int ntri = TriangleIndices.Count;
            for (int i = 0; i < ntri; i += 3)
            {
                int i0 = TriangleIndices[i];
                int i1 = TriangleIndices[i + 1];
                int i2 = TriangleIndices[i + 2];
                var p0 = Positions[i0];
                var p1 = Positions[i1];
                var p2 = Positions[i2];
                var v01 = p1 - p0;
                var v12 = p2 - p1;
                var v20 = p0 - p2;
                var p01 = p0 + v01 * 0.5;
                var p12 = p1 + v12 * 0.5;
                var p20 = p2 + v20 * 0.5;
                var m = new Point3D((p0.X + p1.X + p2.X) / 3, (p0.Y + p1.Y + p2.Y) / 3, (p0.Z + p1.Z + p2.Z) / 3);

                int i01 = im + 1;
                int i12 = im + 2;
                int i20 = im + 3;

                Positions.Add(m);
                Positions.Add(p01);
                Positions.Add(p12);
                Positions.Add(p20);

                if (normals != null)
                {
                    var n = Normals[i0];
                    Normals.Add(n);
                    Normals.Add(n);
                    Normals.Add(n);
                    Normals.Add(n);
                }
                if (textureCoordinates != null)
                {
                    var uv0 = TextureCoordinates[i0];
                    var uv1 = TextureCoordinates[i0 + 1];
                    var uv2 = TextureCoordinates[i0 + 2];
                    var t01 = uv1 - uv0;
                    var t12 = uv2 - uv1;
                    var t20 = uv0 - uv2;
                    var uv01 = uv0 + t01 * 0.5;
                    var uv12 = uv1 + t12 * 0.5;
                    var uv20 = uv2 + t20 * 0.5;
                    var uvm = new Point((uv0.X + uv1.X) * 0.5, (uv0.Y + uv1.Y) * 0.5);
                    TextureCoordinates.Add(uvm);
                    TextureCoordinates.Add(uv01);
                    TextureCoordinates.Add(uv12);
                    TextureCoordinates.Add(uv20);
                }

                // TriangleIndices[i ] = i0;
                TriangleIndices[i + 1] = i01;
                TriangleIndices[i + 2] = im;

                TriangleIndices.Add(i01);
                TriangleIndices.Add(i1);
                TriangleIndices.Add(im);

                TriangleIndices.Add(i1);
                TriangleIndices.Add(i12);
                TriangleIndices.Add(im);

                TriangleIndices.Add(i12);
                TriangleIndices.Add(i2);
                TriangleIndices.Add(im);

                TriangleIndices.Add(i2);
                TriangleIndices.Add(i20);
                TriangleIndices.Add(im);

                TriangleIndices.Add(i20);
                TriangleIndices.Add(i0);
                TriangleIndices.Add(im);

                im += 4;
            }
        }
    }
}
